package ca.tatham.senssim.diffusion;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import ca.tatham.senssim.SensorType;
import ca.tatham.senssim.Simulation;
import ca.tatham.senssim.diffusion.InterestMessage.Interest;
import ca.tatham.senssim.util.Address;

public class GradientCache
{
  private final Map<SensorType, GradientList> m_interests = new HashMap<SensorType, GradientList>();
  private final Address m_address;

  GradientCache(Address address)
  {
    m_address = address;
  }

  public Gradient addOrUpdateGradient(Address source, Interest interest,
      int startTime)
  {
    Gradient gradient = new Gradient(source, interest.interval,
        interest.duration, startTime);
    // Simulation.log().debug("Creating : " + gradient);

    GradientList gradients = getGradientList(interest.type);
    gradients.addOrUpdateGradient(source, gradient);
    return gradient;
  }

  private GradientList getGradientList(SensorType type)
  {
    GradientList gradients = m_interests.get(type);
    if (gradients == null)
    {
      gradients = new GradientList();
      m_interests.put(type, gradients);
    }
    return gradients;
  }

  public void remove(SensorType type)
  {
    GradientList gradients = m_interests.remove(type);
    Simulation.log("Removing " + gradients);
  }

  public GradientList get(SensorType type)
  {
    return m_interests.get(type);
  }

  void cleanup()
  {
    for (Iterator<GradientList> it = m_interests.values().iterator(); it
        .hasNext();)
    {
      GradientList gradients = it.next();
      for (Iterator<Gradient> jt = gradients.iterator(); jt.hasNext();)
      {
        Gradient gradient = jt.next();
        if (gradient.isExpired())
        {
          Simulation.log(m_address + ": Removing " + gradient);
          jt.remove();
        }
      }
      if (gradients.isEmpty())
      {
        it.remove();
      }
    }
  }

  public class GradientList
  {
    private final Map<Address, Gradient> m_gradients = new HashMap<Address, Gradient>();

    GradientList()
    {
      // do nothing
    }

    void addOrUpdateGradient(Address source, Gradient gradient)
    {
      Gradient existing = m_gradients.put(source, gradient);
      if (existing != null)
      {
        if (existing.getDuration() != gradient.getDuration()
            && existing.getInterval() != gradient.getInterval())
        {
          Simulation.log(m_address + ": " + existing + " replaced with "
              + gradient);
        }
        else if (existing.getStartTime() != gradient.getStartTime())
        {
          Simulation.log(m_address + ": " + existing + " extended from "
              + existing.getStartTime() + " to " + gradient.getStartTime());
        }
      }
    }

    public Gradient getGradient(Address address)
    {
      return m_gradients.get(address);
    }

    public Iterator<Gradient> iterator()
    {
      return m_gradients.values().iterator();
    }

    public boolean isEmpty()
    {
      return m_gradients.isEmpty();
    }

    public Gradient getFastestGradient()
    {
      Gradient result = null;
      for (Gradient gradient : m_gradients.values())
      {
        if (result == null || gradient.getInterval() < result.getInterval())
        {
          result = gradient;
        }
      }
      return result;
    }

  }
}